/*
 * PuyoWindow.java
 *
 * Created on August 2, 2004 , PM 10:27
 */

/**package name of Puyo game*/
package Puyo;

import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.InputStream;

/**
 * Puyo's Main Window, manage user interfaces such as keystroke events, image rendering
 * @author  Ming Li
 */
public class PuyoWindow extends GameCanvas implements Runnable{
    
  boolean showGameOver = false;
  
  boolean paused = false;
  
  private boolean fullScreen = false;
  
  /**contains game data and game logic*/
  PuyoLogic puyoLogic = null;

  /** Original Puyo images loaded from resources, They are red, green, yellow, blue.
   *  We need to keep the original one because we need to handle sizeChanged, for
   *  which the scaling should be based on the original imaged to get better image quality.
   */
  public Image puyoImagesOriginal[];
  
  /** Puyo images adapted for the current screen size*/
  public Image puyoImages[];
  
  /** game level. Increase 1 if score increase greater than 50 */
  private int gameLevel = 0;
  
  /**controlling the game speed. the smaller, the faster*/
  private int timeInterval = 200;
 
  /**speed change rate control*/
  double speedChangeRate = 1.1;

  private volatile Thread thread;

  /**an observer which is interested in the event sent by this class*/
  private Observer observer = null;
  
  private PuyoConfig puyoConfig; //reference to the puyoConfig in PuyoMIDlet
  
  private Font smallBoldFont;
  private Font smallFont;
  private Font boldFont;
  
  ///player which plays the midi background music
  private Player player;
  
  ///player which plays sound effect after a block is removed
  private Player wavPlayer;
  
  ///magic number for mute the midi musie
  final private int magicMute = 999;
  
  private Command pauseCommand;
  private Command resumeCommand;
  /**constructor*/
  public PuyoWindow() {
     super(false); 
     smallBoldFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
     boldFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM);
     smallFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);     
     puyoImagesOriginal = new Image[4];
     try{         
         puyoImagesOriginal[0] =  Image.createImage("/Puyo/images/puyo_red.png");
         puyoImagesOriginal[1] =  Image.createImage("/Puyo/images/puyo_green.png");
         puyoImagesOriginal[2] =  Image.createImage("/Puyo/images/puyo_blue.png");
         puyoImagesOriginal[3] =  Image.createImage("/Puyo/images/puyo_yellow.png");
     } catch (java.io.IOException exception) {
          exception.printStackTrace();
     }
     puyoImages = new Image[4];     
     PuyoSettings.init(this);     
     for (int i=0; i< 4; i++){      
          puyoImages[i] = ScaleImage.scaleImage(puyoImagesOriginal[i],PuyoSettings.ballWidth,PuyoSettings.ballHeight);
     }
  }

  public void Init(PuyoConfig config) {
     puyoLogic = new PuyoLogic();  
     puyoConfig = config;
     showGameOver = false;
     paused = false;
     fullScreen = false;
     setFullScreenMode(fullScreen);
     timeInterval = 200;
     gameLevel = 0;
     //if pause/resume has been removed after "game over", we need to add pause again
     removeCommand(getPauseCommand());
     removeCommand(getResumeCommand());
     addCommand(getPauseCommand()); 
     try {
        closeSound();
        InputStream is = getClass().getResourceAsStream("/Puyo/sounds/Background.mid");
        player = Manager.createPlayer(is,"audio/midi");
        player.prefetch(); // prefetch
        player.realize(); // realize
        
        VolumeControl vc;
        vc = (VolumeControl) player.getControl("VolumeControl");
        vc.setLevel(puyoConfig.getSoundVolume());
        player.setLoopCount(-1); // play indefinitely
        
        is = getClass().getResourceAsStream("/Puyo/sounds/laser.wav");
        wavPlayer = Manager.createPlayer(is,"audio/x-wav");
        if(wavPlayer == null) //second chance
            wavPlayer = Manager.createPlayer(is,"audio/wav");
        if(wavPlayer != null){
            wavPlayer.prefetch(); // prefetch
            wavPlayer.realize(); // realize

            vc = (VolumeControl) wavPlayer.getControl("VolumeControl");
            vc.setLevel(puyoConfig.getSoundVolume());
            wavPlayer.setLoopCount(1); // play only once
        }
        
        player.start(); // start the background music
    } catch (Exception ex) {
        closeSound();
    }    
  }
  
  /**tune up game speed*/
  public void speedUp(){
     timeInterval /= speedChangeRate;
  }
    
  /**tune down game speed*/
  public void speedDown(){
     timeInterval *= speedChangeRate;
  }
 
  public void keyPressed(int keyCode) {
      if( this.paused )
          return;
      switch (getGameAction(keyCode)) {
	case Canvas.UP:
	   puyoLogic.rotateCW();
	    break;
	case Canvas.DOWN:
	     puyoLogic.rotateCCW();
	    break;
	case Canvas.LEFT:
	    puyoLogic.moveLeft();
	    break;
	case Canvas.RIGHT:
	    puyoLogic.moveRight();
	    break;
        case Canvas.FIRE:
            puyoLogic.drop();
            break;
        default:
	    // There is no game action.. Use keypad constants instead
	    switch (keyCode) {
	    case Canvas.KEY_NUM2:
		puyoLogic.rotateCW();
		break;
	    case Canvas.KEY_NUM8:
		puyoLogic.rotateCCW();
		break;
	    case Canvas.KEY_NUM4:
		puyoLogic.moveLeft();
		break;
	    case Canvas.KEY_NUM6:
		puyoLogic.moveRight();
		break;
            case Canvas.KEY_NUM1:
                changeVolume(-20);
                break;
            case Canvas.KEY_NUM3:
                changeVolume(20);
                break;    
            case Canvas.KEY_NUM7:
                changeVolume(this.magicMute);
                break;
            case Canvas.KEY_NUM5:
                puyoLogic.drop();
                break;
            case Canvas.KEY_NUM9:
                fullScreen = !fullScreen;
                setFullScreenMode(fullScreen);
                break;
            case KEY_STAR:
                //speedUp();
                break;
            case KEY_POUND:
                //speedDown();
                break;       
	    }
	    break;
	}
   }
  
  public void sizeChanged(int w, int h){
      if ( PuyoSettings.canvasWidth != w || PuyoSettings.canvasHeight != h) {          
          PuyoSettings.init(this);
          for (int i=0; i< 4; i++){      
               puyoImages[i] = ScaleImage.scaleImage(
                       puyoImagesOriginal[i],PuyoSettings.ballWidth,PuyoSettings.ballHeight);
          }
      }
  }
          
  /**draw puyo images according to the stored game data*/
  public void updateGameScreen(Graphics g,boolean draw_ToBeRemoved){
     // clear background 
     g.setColor(0xffffff);
     g.fillRect(0,0,PuyoSettings.canvasWidth,PuyoSettings.canvasHeight);            
     
     // draw a frame enclosing the board
     g.setColor(0xff0000); 
     g.drawRect(0,0,PuyoSettings.boardWidthInPixel() + 1,
                     PuyoSettings.boardHeightInPixel() + 1 );
     
     // draw the balls
     g.translate(1,1); //translate the coordsys so that the balls are not overlapping with the frame
     drawBalls(g,draw_ToBeRemoved); 
     g.translate(-1,-1); //restore the coordsys
     int floorThickness = PuyoSettings.floorThickness();
     if(floorThickness > 0){
       g.setColor(0x0000ff); //blue floor
       g.fillRect(0,PuyoSettings.boardHeightInPixel(),
                   PuyoSettings.canvasWidth,floorThickness);
     }

     if(showGameOver){
      g.setColor(0xff0000);
      g.setFont(boldFont);
      g.drawString("Game Over",PuyoSettings.boardWidthInPixel()/2 - g.getFont().stringWidth("Game") ,PuyoSettings.canvasHeight/2, 
                    Graphics.TOP | Graphics.LEFT);    
     }
     
     if(puyoLogic != null){
      g.setColor(0xff00ff);
      g.setFont(smallBoldFont);
      int nMaxWidth = g.getFont().stringWidth("SuperGamer0");
      int nStartWidth = Math.max(PuyoSettings.boardWidthInPixel() + 2, PuyoSettings.canvasWidth - nMaxWidth);
      
      int nStartHeight = PuyoSettings.canvasHeight > 200 ? 10 : 5;
      int nGap = PuyoSettings.canvasHeight > 200 ? 40 : 35;
      g.drawString(LocalizationSupport.getMessage("SCORE_PLAYER")+":",nStartWidth,nStartHeight, Graphics.TOP | Graphics.LEFT); 
      g.drawString(LocalizationSupport.getMessage("SCORE_SCORE")+":",nStartWidth,nStartHeight+nGap, Graphics.TOP | Graphics.LEFT);
      g.drawString(LocalizationSupport.getMessage("SCORE_LEVEL")+":",nStartWidth,nStartHeight+nGap+nGap, Graphics.TOP | Graphics.LEFT);
      
      int nStartHeightValue = PuyoSettings.canvasHeight > 200 ? 26 : 21;
      g.setFont(smallFont);
      g.drawString(puyoConfig.getCurrentPlayerName(),nStartWidth,nStartHeightValue, Graphics.TOP | Graphics.LEFT);      
      g.drawString(String.valueOf(puyoLogic.getScore()),nStartWidth,nStartHeightValue+nGap, Graphics.TOP | Graphics.LEFT);
      g.drawString(String.valueOf(gameLevel),nStartWidth,nStartHeightValue+nGap+nGap, Graphics.TOP | Graphics.LEFT); 
      int volumeControlWidth = ((VolumeControl) player.getControl("VolumeControl")).getLevel()*2/5;
      
      int nCurrentHeight = nStartHeight+nGap+nGap+nGap;
      //draw a amplifier sign
      g.fillRect(nStartWidth, nCurrentHeight + 3, 4, 5);
      g.fillTriangle(nStartWidth, nCurrentHeight + 5, nStartWidth + 8, nCurrentHeight - 2, nStartWidth + 8, nCurrentHeight + 12);
      if(volumeControlWidth != 0)
          g.fillRect(nStartWidth + 13, nCurrentHeight , volumeControlWidth , 10  );
      else {
          //use a slant line to indicate mute
          g.drawLine(nStartWidth, nCurrentHeight + 10, nStartWidth + 10, nCurrentHeight);
      }
      g.setFont(Font.getDefaultFont());
     }
     flushGraphics();

  }
  
  private int color2ImageIndex(int color){
      return color-1;
  }
  
  private void drawBalls(Graphics g,boolean draw_ToBeRemoved){
      if(puyoLogic != null ){
          //the boolean running is to prevent null pointer
        for(int i=0;i<puyoLogic.numDroppingBalls;i++){   
            if(puyoLogic.droppingBalls[i]!=null){
                int x = puyoLogic.droppingBalls[i].x;
                int y = puyoLogic.droppingBalls[i].y;
                g.drawImage(puyoImages[color2ImageIndex(puyoLogic.droppingBalls[i].color)],
                            (x-1)*PuyoSettings.ballWidth,                        
                            (PuyoSettings.boardHeight-y)*PuyoSettings.ballHeight,
                            Graphics.TOP | Graphics.LEFT);

            }
        }
        for(int i=1;i<PuyoSettings.boardWidth+1;i++)
            for(int j=1;j<PuyoSettings.boardHeight+1;j++)
                if(puyoLogic.accumBalls[i][j] != puyoLogic.puyoEmpty){
                    if( (puyoLogic.accumBalls[i][j] & puyoLogic.flagToBeRemoved) != 0 )  {    
                        if( draw_ToBeRemoved ) {
                           
                            int image_index = color2ImageIndex(puyoLogic.accumBalls[i][j] 
                                                            & ~puyoLogic.flagToBeRemoved );
                            g.drawImage(puyoImages[ image_index ],
                                    (i-1)*PuyoSettings.ballWidth,
                                    (PuyoSettings.boardHeight-j)*PuyoSettings.ballHeight,
                                    Graphics.TOP | Graphics.LEFT);
                        }
                        else
                        {
                            g.setColor(0xffffff);
                            g.fillRect((i-1)*PuyoSettings.ballWidth, 
                                       (PuyoSettings.boardHeight-j)*PuyoSettings.ballHeight, 
                                       PuyoSettings.ballWidth, PuyoSettings.ballHeight);
                        }                        
                    }
                    else
                    {
                        g.drawImage(puyoImages[color2ImageIndex(puyoLogic.accumBalls[i][j])],
                                    (i-1)*PuyoSettings.ballWidth,
                                    (PuyoSettings.boardHeight-j)*PuyoSettings.ballHeight,
                                    Graphics.TOP | Graphics.LEFT);
                    }
                }
      }
  }

  ///When the game canvas is hidden, stop the thread.
  protected void hideNotify(){
      thread = null;
      pauseSound();
  }

  /** When the canvas is shown, start a thread to
  *run the game loop.
  */
  protected void showNotify(){
     thread = new Thread( this );
     thread.start();
     try {
	    if (player != null){
		player.start();               
            }
     } catch (MediaException ex){}
  }
  
  private void delay(int mseconds){
       try {
	   Thread.currentThread().sleep(mseconds);
       } catch(Exception e) {}
  }
  /**pause switch*/
  public void changePauseState(){
       paused = !paused;
       if( paused ){
           pauseSound();
       }
       else{
           try {
	     player.start();
	   } catch (Exception ex) {}              
       }
  }  
  ///game loop
  public void run() {
     Graphics myGraphics = getGraphics();
     Thread thisThread = Thread.currentThread();
     while(thread == thisThread){      
       updateGameScreen(myGraphics,true); 
       delay(timeInterval);
       if(!paused && puyoLogic.drop()){ 
            updateGameScreen(myGraphics,true);             
            while(puyoLogic.checkAccumBalls()){ 
                 delay(2*timeInterval);  
                 try {
                     if(wavPlayer != null)
                        wavPlayer.start();
                 } catch(Exception e) {}
                 updateGameScreen(myGraphics,true); 
                 
                 // blink twice
                 for( int i = 0; i < 2; i++ )
                 {
                    delay( timeInterval * 3 / 2 );
                    updateGameScreen(myGraphics,false);                     
                    delay( timeInterval * 3 / 2 );
                    updateGameScreen(myGraphics,true);                    
                 }
                 
                 // Remove the balled marked as "to be removed" and let the ball fall
                 puyoLogic.removeBalls();               
                 while(puyoLogic.freeFalling())
                 {
                 }                
                 delay( timeInterval * 3 / 2 );                  
                 updateGameScreen(myGraphics,true); 
            }
            
            //update game level
            if( puyoLogic.getScore()/50 > gameLevel )
            {
                gameLevel++;
                speedUp();
            }
            //check if game over
            if(!puyoLogic.initDroppingBalls()) {
                 showGameOver = true;
                 removeCommand(getPauseCommand());
                 removeCommand(getResumeCommand());
                 updateGameScreen(myGraphics,true);
                 delay(2500);
                 notifyScoreObserver(); 
                 break;            
            }                  
        }        
        updateGameScreen(myGraphics,true);  
        delay(timeInterval);         
    }
  } 

    public boolean notifyScoreObserver() {
        pauseSound(); //we take this chance to pause the sound
        if(observer!=null){
           return observer.notifyScore(puyoLogic.getScore(), (byte)gameLevel); //now level is not implemented, simply set to 0
        }
        return false;
    }

    public void registerObserver(Observer anObserver) {
        observer = anObserver;
    }
  
    public void closeSound(){
        try {
	    if (player != null) {
		player.close();
                player = null;
            }
            if (wavPlayer != null) {
		wavPlayer.close();
                wavPlayer = null;
            }
	} catch (Exception ex) {}   
    }
    
    public void pauseSound(){
       try {
	    if (player != null){
		player.stop();               
            }
	} catch (MediaException ex){}
    }
    
     private void changeVolume(int diff) {
	VolumeControl vc;

	if ( player != null) {
	    vc = (VolumeControl) player.getControl("VolumeControl");
	    if (vc != null) {
		short cv = (short)vc.getLevel();
                if( diff == magicMute )
                    cv = 0;
                else{
                    if(cv==0 && diff < 0 || cv==100 && diff>0) 
                        return;
                    cv += diff;              
                }
                puyoConfig.setSoundVolume(cv);
		vc.setLevel(cv);               	
	    }
            vc = (VolumeControl) wavPlayer.getControl("VolumeControl");	    
            if (vc != null) {
		short cv = (short)vc.getLevel();
                if( diff == magicMute )
                    cv = 0;
                else{
                    if(cv==0 && diff < 0 || cv==100 && diff>0) 
                        return;
                    cv += diff;
                }              
                vc.setLevel(cv);	
	    }
	}
    }
     

   public Command getPauseCommand() {
       if (pauseCommand == null) {
           pauseCommand = new Command(LocalizationSupport.getMessage("COMMAND_PAUSE"), 
                                      Command.STOP, 1);
       }
       return pauseCommand;
   }



   public Command getResumeCommand() {
       if (resumeCommand == null) {
           resumeCommand = new Command(LocalizationSupport.getMessage("COMMAND_RESUME"),
                                       Command.STOP, 1);
       }
       return resumeCommand;
   }
} //class end
